/***************************************************************************
 *   Copyleft 2006-2007 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./Mesh.h"

/**************************************************************
* Constructors                                                *
***************************************************************/
Mesh::Mesh(): Object3D() {
	mColor = Color::ToWinColor(WHITE);
	mSurfaceColor = RGB(0, 255, 127);
	mMeshColor = RGB(255, 0, 0);
	mIsPeriodic = false;
	mMeshVisible = true;
	mControlPointsVisible = true;
	mControlPointsCoordinatesVisibility = true;
	mU = 5;
	mV = 5;
}

/*virtual*/ Mesh::~Mesh() { }

/**************************************************************
* Operators                                                   *
***************************************************************/


/**************************************************************
* Other helpful functions                                     *
***************************************************************/
cVoid Mesh::SelectPoint(cInt iRow, cInt iCol, SELECTION_TYPE bSelected) {
	if (iRow < 0 || iRow >= mMesh.GetRowSize() || iCol < 0 || iCol >= mMesh.GetColSize()) {
		return;
	}
	if (mMesh(iRow, iCol).mSelected != PERS_SELECTION) {
		mMesh(iRow, iCol).mSelected = bSelected;
	}
}

Mesh::SELECTION_TYPE Mesh::SelectPoint(cInt iRow, cInt iCol, cBool bSelect) {
	switch(mMesh(iRow, iCol).mSelected) {
		case NO_SELECTION:
		case TEMP_SELECTION:
			mMesh(iRow, iCol).mSelected = PERS_SELECTION;
			break;
		default:
			mMesh(iRow, iCol).mSelected = TEMP_SELECTION;
			break;
	}

	return mMesh(iRow, iCol).mSelected;
}

cVoid Mesh::DeselectPoints() {
	for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
		for (cInt j = 0; j < mMesh.GetColSize(); j++) {
			mMesh(i, j).mSelected = NO_SELECTION;
		}
	}
}

/*virtual*/ cVoid Mesh::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane) {
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	Point4D a, b;
	DynamicArray2D<Point4D> tab(mMesh.GetRowSize(), mMesh.GetColSize());
	
	for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
		for (cInt j = 0; j < mMesh.GetColSize(); j++) {
			a = mMesh(i, j).mPoint;
			a.mW = 1.0f;
			tab(i, j) = matrix * a;
		}
	}
	if (mMeshVisible) {
		for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
			for (cInt j = 0; j < mMesh.GetColSize(); j++) {
				if (i + 1 < mMesh.GetRowSize()) {
					a = tab(i, j);
					b = tab(i + 1, j);
					if (Clip(a, b, plane)) {
						renderer.DrawROPLine((cInt)(a.mX/a.mW) + iHalfWidth, (cInt)(-a.mY/a.mW) + iHalfHeight,
							(cInt)(b.mX/b.mW) + iHalfWidth, (cInt)(-b.mY/b.mW) + iHalfHeight, mMeshColor);
					}
				}
				if (mIsPeriodic || j + 1 < mMesh.GetColSize()) {
					a = tab(i, j);
					b = tab(i, mIsPeriodic ? ((j + 1) % mMesh.GetColSize()) : (j + 1));
					if (Clip(a, b, plane)) {
						renderer.DrawROPLine((cInt)(a.mX/a.mW) + iHalfWidth, (cInt)(-a.mY/a.mW) + iHalfHeight,
							(cInt)(b.mX/b.mW) + iHalfWidth, (cInt)(-b.mY/b.mW) + iHalfHeight, mMeshColor);
					}
				}
			}
		}
	}
	RenderControlPoints(renderer, matrix, plane, mColor);
}

/*virtual*/ cVoid Mesh::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane, cWinColor color) {
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	DynamicArray2D<Point4D> tab(mMesh.GetRowSize(), mMesh.GetColSize());
	Point4D a, b;

	for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
		for (cInt j = 0; j < mMesh.GetColSize(); j++) {
			a = mMesh(i, j).mPoint;
			a.mW = 1.0f;
			tab(i, j) = matrix * a;
		}
	}
	if (mMeshVisible) {
		for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
			for (cInt j = 0; j < mMesh.GetColSize(); j++) {
				if (i + 1 < mMesh.GetRowSize()) {
					a = tab(i, j);
					b = tab(i + 1, j);
					if (Clip(a, b, plane)) {
						renderer.DrawROPLine((cInt)(a.mX/a.mW) + iHalfWidth, (cInt)(-a.mY/a.mW) + iHalfHeight,
							(cInt)(b.mX/b.mW) + iHalfWidth, (cInt)(-b.mY/b.mW) + iHalfHeight, color);
					}
				}
				if (mIsPeriodic || j + 1 < mMesh.GetColSize()) {
					a = tab(i, j);
					b = tab(i, mIsPeriodic ? ((j + 1) % mMesh.GetColSize()) : (j + 1));
					if (Clip(a, b, plane)) {
						renderer.DrawROPLine((cInt)(a.mX/a.mW) + iHalfWidth, (cInt)(-a.mY/a.mW) + iHalfHeight,
							(cInt)(b.mX/b.mW) + iHalfWidth, (cInt)(-b.mY/b.mW) + iHalfHeight, color);
					}
				}
			}
		}
	}
	RenderControlPoints(renderer, matrix, plane, color);
}

cVoid Mesh::RenderControlPoints(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane, 
								cWinColor color) {
	if (mControlPointsVisible) {
		Point4D a;
		cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
		for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
			for (cInt j = 0; j < mMesh.GetColSize(); j++) {
				a = matrix * mMesh(i, j).mPoint;
				if (a.mW < plane.GetNearClippingPlane()) {
					continue;
				}
				a.Normalize();
				a.mX += iHalfWidth;
				a.mY = -a.mY + iHalfHeight;
	
				renderer.DrawPoint(a, color, (cShort) mMesh(i, j).mSelected);
			}
		}
	}
}

cVoid Mesh::Create(AbstractCurve* curve1, AbstractCurve* curve2) {
	if (curve1->GetCurveNrOfPoints() == 0 || curve2->GetCurveNrOfPoints() == 0) {
		return;
	}

	mMesh = DynamicArray2D<MeshPoint>(curve1->GetCurveNrOfPoints(), curve2->GetCurveNrOfPoints());
	Point4D v;

	for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
		v = curve1->GetCurvePoint(i) - curve2->GetCurvePoint(0);
		for (cInt j = 0; j < mMesh.GetColSize(); j++) {
			mMesh(i, j).mPoint = curve2->GetCurvePoint(j) + v;
			mMesh(i, j).mSelected = NO_SELECTION;
		}
	}
}

/*virtual*/ cVoid Mesh::Init(cInt iRow, cInt iCol) {
	if (!iRow || !iCol) {
		return;
	}
	mMesh.Clear();
	mMesh = DynamicArray2D<MeshPoint>(iRow, iCol);
	for (cInt i = 0; i < iRow; i++) {
		for (cInt j = 0; j < iCol; j++) {
			mMesh(i, j).mSelected = NO_SELECTION;
		}
	}
}

/*virtual*/ Point4D Mesh::GetMeshPoint(cInt iRow, cInt iCol) {
	if (iRow < 0 || iRow >= mMesh.GetRowSize() || iCol < 0 || iCol >= mMesh.GetColSize()) {
		throw std::exception();
	}

	return mMesh(iRow, iCol).mPoint;
}

/*virtual*/ cVoid Mesh::UpdateMeshPoint(cInt iRow, cInt iCol, Point4D point) {
	if (iRow < 0 || iRow >= mMesh.GetRowSize() || iCol < 0 || iCol >= mMesh.GetColSize()) {
		return;
	}
	mMesh(iRow, iCol).mPoint = point;
}

/*virtual*/ cVoid Mesh::SetMeshPoint(cInt iRow, cInt iCol, Point4D point) {
	if (iRow < 0 || iRow >= mMesh.GetRowSize() || iCol < 0 || iCol >= mMesh.GetColSize()) {
		return;
	}
	mMesh(iRow, iCol).mPoint = point;
	mMesh(iRow, iCol).mSelected = NO_SELECTION;
}

/*virtual*/ cVoid Mesh::SwitchControlPointsVisibility() {
	mControlPointsVisible = !mControlPointsVisible;
}

/*virtual*/ cVoid Mesh::SwitchMeshVisibility() {
	mMeshVisible = !mMeshVisible;
}

/*virtual*/ cVoid Mesh::SwitchControlPointsCoordinatesVisibility() {
	mControlPointsCoordinatesVisibility = !mControlPointsCoordinatesVisibility;
}

cBool Mesh::ArePointsCoordinatesVisible() {
	return mControlPointsCoordinatesVisibility;
}

cInt Mesh::GetRowCount() {
	return mMesh.GetRowSize();
}

cInt Mesh::GetColCount() {
	return mMesh.GetColSize();
}

cVoid Mesh::ChangeU(cInt iU) {
	if (mU + iU < 1) {
		return;
	}
	mU += iU;
}

cVoid Mesh::ChangeV(cInt iV) {
	if (mV + iV < 1) {
		return;
	}
	mV += iV;
}

cVoid Mesh::DisplayInfo(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane) { 
	cChar buf[MAX_BUF_SIZE];
	Point4D a;

	if (mVisible) {
		for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
			for (cInt j = 0; j < mMesh.GetColSize(); j++) {
				a = mMesh(i, j).mPoint;
				a = matrix * mMatrix * mObjectMatrix * a;
				if (a.mW < plane.GetNearClippingPlane()) {
					continue;
				}
				a.Normalize();
				a.mX += (renderer.GetWidth() >> 1);
				a.mY = -a.mY + (renderer.GetHeight() >> 1);
				sprintf_s(buf, "[%.0f, %.0f, %.0f]", mMesh(i, j).mPoint.mX,
						mMesh(i, j).mPoint.mY,  mMesh(i, j).mPoint.mZ);
				renderer.DrawString((cInt) a.mX, (cInt) a.mY, buf, 
					(mMesh(i, j).mSelected == NO_SELECTION) ? false : true, true, false);
			}
		}
	}
}

/*virtual*/ cVoid Mesh::SwitchPeriodic() {
	mIsPeriodic = !mIsPeriodic;
}

/*virtual*/ cVoid Mesh::SetPeriodic(cBool bPeriodic) {
	mIsPeriodic = bPeriodic;
}

/*virtual*/ TiXmlElement* Mesh::Serialize(TiXmlElement* parent) {
	TiXmlElement* elem = new TiXmlElement("Mesh");
	Point4D a;

	parent->LinkEndChild(elem);
	elem->SetAttribute("rows", mMesh.GetRowSize());
	elem->SetAttribute("cols", mMesh.GetColSize());
	elem->SetAttribute("closed", mIsPeriodic ? "true" : "false");
	
	for (cInt i = 0; i < mMesh.GetRowSize(); i++) {
		for (cInt j = 0; j < mMesh.GetColSize(); j++) {
			a = mMesh(i, j).mPoint;
			a = mMatrix * mObjectMatrix * a;
			a.Serialize(elem);
		}
	}

	return elem;
}